﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using Microsoft.Practices.CompositeUI.EventBroker;
using Kent.Boogaart.HelperTrinity;
using WPFCABShell.Shell.Interface.Diagnostics;
using WPFCABShell.Shell.Interface.Services.StatusBar;

namespace WPFCABShell.Shell.Services.StatusBar
{
	/// <summary>
	/// An implementation of <see cref="IStatusBarService"/> for the WPF CAB shell.
	/// </summary>
	public sealed class StatusBarService : IStatusBarService
	{
		private readonly PrioritizedList<MessageOperation> _messageOperations;
		private readonly PrioritizedList<MessageOperation> _secondaryMessageOperations;
		private readonly PrioritizedList<MessageOperation> _ternaryMessageOperations;
		private readonly PrioritizedList<ProgressOperation> _progressOperations;
		private static readonly StatusPriority[] _validPriorities = new StatusPriority[] { StatusPriority.Low, StatusPriority.Normal, StatusPriority.High };
		private static readonly Log _log = Log.CreateForType(typeof(StatusBarService));

		[EventPublication(Events.MessageChanged)]
		public event EventHandler<MessageEventArgs> MessageChanged;

		[EventPublication(Events.SecondaryMessageChanged)]
		public event EventHandler<MessageEventArgs> SecondaryMessageChanged;

		[EventPublication(Events.TernaryMessageChanged)]
		public event EventHandler<MessageEventArgs> TernaryMessageChanged;

		[EventPublication(Events.ProgressChanged)]
		public event EventHandler<ProgressEventArgs> ProgressChanged;

		public StatusBarService()
		{
			_messageOperations = new PrioritizedList<MessageOperation>();
			_secondaryMessageOperations = new PrioritizedList<MessageOperation>();
			_ternaryMessageOperations = new PrioritizedList<MessageOperation>();
			_progressOperations = new PrioritizedList<ProgressOperation>();

			_messageOperations.TopItemChanged += delegate
			{
				OnMessageChanged(MessageType.Primary);
			};

			_secondaryMessageOperations.TopItemChanged += delegate
			{
				OnMessageChanged(MessageType.Secondary);
			};

			_ternaryMessageOperations.TopItemChanged += delegate
			{
				OnMessageChanged(MessageType.Ternary);
			};

			_progressOperations.TopItemChanged += delegate
			{
				OnProgressChanged();
			};
		}

		public IMessageOperation BeginMessageOperation()
		{
			return BeginMessageOperation(StatusPriority.Normal);
		}

		public IMessageOperation BeginMessageOperation(StatusPriority priority)
		{
			ArgumentHelper.AssertEnumMember(priority, "priority", _validPriorities);
			_log.Verbose("Beginning message operation with priority {0}.", priority);
			return CreateMessageOperation(MessageType.Primary, priority);
		}

		public IMessageOperation BeginSecondaryMessageOperation()
		{
			return BeginSecondaryMessageOperation(StatusPriority.Normal);
		}

		public IMessageOperation BeginSecondaryMessageOperation(StatusPriority priority)
		{
			ArgumentHelper.AssertEnumMember(priority, "priority", _validPriorities);
			_log.Verbose("Beginning secondary message operation with priority {0}.", priority);
			return CreateMessageOperation(MessageType.Secondary, priority);
		}

		public IMessageOperation BeginTernaryMessageOperation()
		{
			return BeginTernaryMessageOperation(StatusPriority.Normal);
		}

		public IMessageOperation BeginTernaryMessageOperation(StatusPriority priority)
		{
			ArgumentHelper.AssertEnumMember(priority, "priority", _validPriorities);
			_log.Verbose("Beginning ternary operation with priority {0}.", priority);
			return CreateMessageOperation(MessageType.Ternary, priority);
		}

		public IProgressOperation BeginProgressOperation()
		{
			return BeginProgressOperation(StatusPriority.Normal);
		}

		public IProgressOperation BeginProgressOperation(StatusPriority priority)
		{
			ArgumentHelper.AssertEnumMember(priority, "priority", _validPriorities);
			_log.Verbose("Beginning progress operation with priority {0}.", priority);
			ProgressOperation progressOperation = new ProgressOperation(this, priority);
			_progressOperations.Add(progressOperation);
			progressOperation.PropertyChanged += progressOperation_PropertyChanged;

			return progressOperation;
		}

		private void progressOperation_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			OnProgressChanged();
		}

		public void EndOperation(IStatusBarOperation operation)
		{
			ArgumentHelper.AssertNotNull(operation, "operation");
			RemoveOperation(operation);
		}

		private void RemoveOperation(IStatusBarOperation operation)
		{
			operation.Dispose();

			if (operation is MessageOperation)
			{
				_log.Verbose("Ending message operation with priority {0}.", operation.Priority);
				MessageOperation messageOperation = operation as MessageOperation;
				GetMessageListForType(messageOperation.Type).Remove(messageOperation);
				messageOperation.PropertyChanged -= messageOperation_PropertyChanged;
			}
			else if (operation is ProgressOperation)
			{
				_log.Verbose("Ending progress operation with priority {0}.", operation.Priority);
				ProgressOperation progressOperation = operation as ProgressOperation;
				_progressOperations.Remove(progressOperation);
				progressOperation.PropertyChanged -= progressOperation_PropertyChanged;
			}
			else
			{
				ExceptionHelper.Throw("CannotEndOperation");
			}
		}

		private void OnMessageChanged(MessageType type)
		{
			PrioritizedList<MessageOperation> list = GetMessageListForType(type);
			MessageOperation messageOperation = list.TopItem;
			MessageEventArgs e = MessageEventArgs.Empty;

			if (messageOperation != null)
			{
				e = new MessageEventArgs(messageOperation.Text, messageOperation.Image);
			}

			switch (type)
			{
				case MessageType.Primary:
					EventHelper.Raise(MessageChanged, this, e);
					break;
				case MessageType.Secondary:
					EventHelper.Raise(SecondaryMessageChanged, this, e);
					break;
				case MessageType.Ternary:
					EventHelper.Raise(TernaryMessageChanged, this, e);
					break;
				default:
					Debug.Assert(false);
					break;
			}
		}

		private void OnProgressChanged()
		{
			ProgressOperation progressOperation = _progressOperations.TopItem;
			ProgressEventArgs e = ProgressEventArgs.Empty;

			if (progressOperation != null)
			{
				e = new ProgressEventArgs(progressOperation.Text, progressOperation.IsIndeterminate, progressOperation.Minimum, progressOperation.Maximum, progressOperation.Value);
			}

			EventHelper.Raise(ProgressChanged, this, e);
		}

		private PrioritizedList<MessageOperation> GetMessageListForType(MessageType type)
		{
			switch (type)
			{
				case MessageType.Primary:
					return _messageOperations;
				case MessageType.Secondary:
					return _secondaryMessageOperations;
				case MessageType.Ternary:
					return _ternaryMessageOperations;
				default:
					Debug.Assert(false);
					return null;
			}
		}

		private MessageOperation CreateMessageOperation(MessageType type, StatusPriority priority)
		{
			PrioritizedList<MessageOperation> list = GetMessageListForType(type);
			MessageOperation messageOperation = new MessageOperation(type, this, priority);
			messageOperation.PropertyChanged += messageOperation_PropertyChanged;
			list.Add(messageOperation);
			return messageOperation;
		}

		private void messageOperation_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			MessageOperation messageOperation = sender as MessageOperation;
			Debug.Assert(messageOperation != null);
			OnMessageChanged(messageOperation.Type);
		}

		#region Helper Types

		private enum MessageType
		{
			Primary,
			Secondary,
			Ternary
		}

		private abstract class StatusBarOperation : IStatusBarOperation, INotifyPropertyChanged
		{
			private readonly StatusBarService _statusBarService;
			private readonly StatusPriority _priority;
			private string _text;
			private bool _disposed;

			public StatusPriority Priority
			{
				get
				{
					return _priority;
				}
			}

			public string Text
			{
				get
				{
					return _text;
				}
				set
				{
					if (value != _text)
					{
						_text = value;
						OnPropertyChanged("Text");
					}
				}
			}

			public event PropertyChangedEventHandler PropertyChanged;

			protected StatusBarOperation(StatusBarService statusBarService, StatusPriority priority)
			{
				_statusBarService = statusBarService;
				_priority = priority;
			}

			public void Dispose()
			{
				if (!_disposed)
				{
					_disposed = true;
					_statusBarService.RemoveOperation(this);
				}
			}

			protected void OnPropertyChanged(string propertyName)
			{
				EventHelper.Raise(PropertyChanged, this, new PropertyChangedEventArgs(propertyName));
			}
		}

		private sealed class MessageOperation : StatusBarOperation, IMessageOperation
		{
			private readonly MessageType _type;
			private object _image;

			public MessageType Type
			{
				get
				{
					return _type;
				}
			}

			public object Image
			{
				get
				{
					return _image;
				}
				set
				{
					if (value != _image)
					{
						_image = value;
						OnPropertyChanged("Image");
					}
				}
			}

			public MessageOperation(MessageType type, StatusBarService statusBarService, StatusPriority priority)
				: base(statusBarService, priority)
			{
				_type = type;
			}
		}

		private sealed class ProgressOperation : StatusBarOperation, IProgressOperation
		{
			private bool _isIndeterminate;
			private double _minimum;
			private double _maximum;
			private double _value;

			public bool IsIndeterminate
			{
				get
				{
					return _isIndeterminate;
				}
				set
				{
					if (value != _isIndeterminate)
					{
						_isIndeterminate = value;
						OnPropertyChanged("IsIndeterminate");
					}
				}
			}

			public double Minimum
			{
				get
				{
					return _minimum;
				}
				set
				{
					if (value != _minimum)
					{
						_minimum = value;
						OnPropertyChanged("Minimum");
					}
				}
			}

			public double Maximum
			{
				get
				{
					return _maximum;
				}
				set
				{
					if (value != _maximum)
					{
						_maximum = value;
						OnPropertyChanged("Maximum");
					}
				}
			}

			public double Value
			{
				get
				{
					return _value;
				}
				set
				{
					if (value != _value)
					{
						_value = value;
						OnPropertyChanged("Value");
					}
				}
			}


			public ProgressOperation(StatusBarService statusBarService, StatusPriority priority)
				: base(statusBarService, priority)
			{
			}
		}

		private sealed class PrioritizedList<T>
			where T : class, IStatusBarOperation
		{
			private readonly List<T> _list;

			public T TopItem
			{
				get
				{
					return _list.Count > 0 ? _list[_list.Count - 1] : null;
				}
			}

			public event EventHandler<EventArgs> TopItemChanged;

			public PrioritizedList()
			{
				_list = new List<T>();
			}

			public void Add(T item)
			{
				Debug.Assert(item != null);
				int insertionIndex = 1 + _list.FindLastIndex(delegate(T existingItem)
				{
					return existingItem.Priority <= item.Priority;
				});

				_list.Insert(insertionIndex, item);

				if (insertionIndex == (_list.Count - 1))
				{
					//inserted at end so the top item changed
					OnTopItemChanged();
				}
			}

			public void Remove(T item)
			{
				int index = _list.IndexOf(item);
				Debug.Assert(index != -1);
				_list.RemoveAt(index);

				if (index == _list.Count)
				{
					//removed last item, so the top item changed
					OnTopItemChanged();
				}
			}

			private void OnTopItemChanged()
			{
				EventHelper.Raise(TopItemChanged, this, EventArgs.Empty);
			}
		}

		#endregion
	}
}
